﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Assignment2
{
    /// <summary>
    /// Provides a multi-celled thread safe buffer for transferring items from a producer to a consuer. 
    /// Implemented as a first in, first out (FIFO) buffer.
    /// </summary>
    public static class MultiCellBuffer
    {
        // price cut event
        public static event OrderNotification order_notify;

        private static int numberOfCells;
        private static string[] dataCells;
        private static bool[] isWriteable;
        private static int currentCellPointer;
        private static Semaphore filledCells;
        private static Semaphore emptyCells;
        private static Semaphore mutex;

        /// <summary>
        /// Constructor to initialize the buffer.
        /// </summary>
        /// <param name="numCells">Number of cells in the buffer as in Int. Must be greater than 0.</param>
        //public static MultiCellBuffer(int numCells)
        public static void init(int numCells)
        {
            numberOfCells = numCells;

            // Initialize filledCells semaphore to 0 filled, capacity of numberOfCells. This is the number of cells that are currently occupied with data
            filledCells = new Semaphore(0, numberOfCells);

            // Initialize emptyCells semaphore to filled to capacity, capacity of numberOfCells. This is the number of cells that are currently empty
            emptyCells = new Semaphore(numberOfCells, numberOfCells);

            // Initialize a semaphore to act like a mutex, for locking the critical section
            mutex = new Semaphore(1, 1);

            // Initialize the currentCellPointer to 0. This points to the most recently used cell.
            currentCellPointer = 0;

            dataCells = new string[numberOfCells];
            isWriteable = new bool[numberOfCells];

            // Initialize all cells to empty
            for (int i = 0; i < numberOfCells; i++)
            {
                dataCells[i] = "";
                isWriteable[i] = true;
            }
        }

        /// <summary>
        /// Adds one item to the buffer. If the buffer is currently full, it waits until a space is available.
        /// </summary>
        /// <param name="orderString">Order as a String.</param>
        public static void setOneCell(string orderString)
        {
            // Add one to the emptyCells semaphore, if none available, wait here until available
            emptyCells.WaitOne();

            // Only allow one thread to be in here at a time
            mutex.WaitOne();

            // Loop through each cell
            for (int i = 0; i < numberOfCells; i++)
            {
                // If the cell is writeable, put the string into the cell and break out of the loop
                if (isWriteable[i])
                {
                    isWriteable[i] = false;
                    dataCells[i] = orderString;

                    break;
                }
            }

            // Allow another thread in here now
            mutex.Release();

            // Release a filled cell semaphore
            filledCells.Release();

            // make sure there is at least 1 subscriber
            if (order_notify != null)
            {
                //Console.WriteLine("Order received by MultiCellBuffer");
                // fire the price cut event
                order_notify();
            }

        }

        /// <summary>
        /// Retrieve item from the buffer.
        /// </summary>
        /// <returns>Item as a String.</returns>
        public static string getOneCell()
        {
            // Add one to the filledCells semaphore, if none available, wait here until available
            filledCells.WaitOne();

            // Only allow one thread to be in here at a time
            mutex.WaitOne();

            string returnString = "ERROR";

            // Loop through all the cells, once one is found to have readable data, stop looping
            for (int i = currentCellPointer; i < numberOfCells; i++)
            {
                // set pointer to point to the next item to pull from the buffer
                currentCellPointer = i + 1;

                // Is the cell filled? If yes, retrieve the contents, break out of loop, and return the string
                if (!isWriteable[i])
                {
                    isWriteable[i] = true;          // reset the cell to be writeable
                    returnString = dataCells[i];    // get the cell contents

                    // Reset the currentCellPointer to 0 if it is at or above the limit
                    if (currentCellPointer >= numberOfCells)
                    {
                        currentCellPointer = 0;
                    }

                    break;
                }
                // If i is at max value, reset it -1 so it will be incremented to 0
                if (currentCellPointer == numberOfCells)
                    i = -1;

                // If we have completed all the loops, break out of the loop
                // This is needed because we reseeded the numberOfCells value artificially
                if (i == numberOfCells)
                    break;
            }

            // Allow another thread in here now
            mutex.Release();

            // Release one of the emptyCells semaphore
            emptyCells.Release();

            return returnString;
        }
    }
}
